package com.example.s3demo.controller

import com.example.s3demo.model.CompleteMultipartUpload
import com.example.s3demo.model.ListBucketsResult
import com.example.s3demo.model.PartETag
import com.example.s3demo.service.S3Service
import com.example.s3demo.utils.*
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.dom4j.DocumentHelper
import org.dom4j.Element
import org.dom4j.io.OutputFormat
import org.dom4j.io.SAXReader
import org.dom4j.io.XMLWriter
import org.slf4j.LoggerFactory
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import java.io.BufferedInputStream
import java.io.IOException
import java.io.OutputStream
import java.io.StringWriter
import java.net.URLDecoder
import java.net.URLEncoder


@RestController
@RequestMapping("/s3")
class S3Controller(
    private val s3Service: S3Service,
) {
    private val log = LoggerFactory.getLogger(S3Controller::class.java)
    // 创建桶
    @PutMapping("/{bucketName}")
    fun createBucket(@PathVariable bucketName:String): ResponseEntity<String> {
        val bucketName = URLDecoder.decode(bucketName, "utf-8")
        s3Service.createBucket(bucketName)
        return ResponseEntity.ok().build()
    }

    // 删除桶
    @DeleteMapping("/{bucketName}")
    fun deleteBucket(@PathVariable bucketName: String):ResponseEntity<String> {
        val bucketName = URLDecoder.decode(bucketName, "utf-8")
        s3Service.deleteBucket(bucketName)
        return ResponseEntity.noContent().build()
    }

    // 文件上传
    @PutMapping("/{bucketName}/**")
    fun putObject(@PathVariable bucketName: String, request: HttpServletRequest): ResponseEntity<String> {
        val bucketName = URLDecoder.decode(bucketName, "utf-8")
        var pageUrl = URLDecoder.decode(request.requestURI, "utf-8")
        if (pageUrl.contains("\\?")) {
            pageUrl = pageUrl.split("\\?".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0]
        }
        val objectKey = pageUrl.replace(request.contextPath + "/s3/" + bucketName + "/", "")
        var copySource = URLDecoder.decode(request.getHeader("x-amz-copy-source")?:"", "utf-8")
        if (StringUtil.isEmpty(copySource)) {
            s3Service.putObject(bucketName, objectKey, request.inputStream)
            return ResponseEntity.ok().build()
        } else {
            if (copySource.contains("\\?")) {
                copySource = copySource.split("\\?".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0]
            }
            val copyList = copySource.split("\\/".toRegex()).dropLastWhile { it.isEmpty() }
                .toTypedArray()
            var sourceBucketName: String? = ""
            for (item in copyList) {
                if (!StringUtil.isEmpty(item)) {
                    sourceBucketName = item
                    break
                }
            }

            val result = StringBuilder()
            for (i in 1 until copyList.size) {
                result.append(copyList[i]).append("/")
            }
            val sourceObjectKey = result.toString()
            s3Service.copyObject(sourceBucketName!!, sourceObjectKey, bucketName, objectKey)

            var xml = ""
            val doc = DocumentHelper.createDocument()
            val root = doc.addElement("CopyObjectResult")
            val lastModified = root.addElement("LastModified")
            lastModified.text = DateUtil.UTCDateFormat()
            val eTag = root.addElement("ETag")
            eTag.text = EncryptUtil.encryptByMD5(copySource)
            val format = OutputFormat.createPrettyPrint()
            format.encoding = "utf-8"
            val out = StringWriter()
            val writer = XMLWriter(out, format)
            writer.write(doc)
            writer.close()
            xml = out.toString()
            out.close()
            return ResponseEntity.ok().contentType(MediaType.APPLICATION_XML).body(xml)
        }
    }

    // 文件删除
    @DeleteMapping("/{bucketName}/**")
    fun relObject(@PathVariable bucketName: String, request: HttpServletRequest): ResponseEntity<String> {
        val bucketName = URLDecoder.decode(bucketName, "utf-8")
        var pageUrl = URLDecoder.decode(request.requestURI, "utf-8")
        if (pageUrl.contains("\\?")) {
            pageUrl = pageUrl.split("\\?".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0]
        }
        val objectKey = pageUrl.replace(request.contextPath + "/s3/" + bucketName + "/", "")
        s3Service.deleteObject(bucketName, objectKey)
        return ResponseEntity.noContent().build()

    }

    // 文件下载
    @GetMapping("/{bucketName}/**")
    fun getObject(@PathVariable bucketName: String, request: HttpServletRequest, response: HttpServletResponse) {
        val bucketName = URLDecoder.decode(bucketName, "utf-8")
        var pageUrl = URLDecoder.decode(request.requestURI, "utf-8")
        if (pageUrl.contains("\\?")) {
            pageUrl = pageUrl.split("\\?".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0]
        }
        val objectKey = pageUrl.replace(request.contextPath + "/s3/" + bucketName + "/", "")
        val objectStream = s3Service.getObject(bucketName, objectKey)!!
        response.setContentType(objectStream.metadata.contentType)
        response.setHeader(
            "Content-Disposition",
            "filename=" + URLEncoder.encode(objectStream.metadata.fileName, "utf-8")
        )
        response.setCharacterEncoding("utf-8")
        response.setContentLength(ConvertOp.convert2Int(objectStream.metadata.contentLength))
        val buff = ByteArray(ConvertOp.convert2Int(objectStream.metadata.contentLength))
        var bufferedInputStream: BufferedInputStream? = null
        var outputStream: OutputStream? = null
        try {
            outputStream = response.getOutputStream()
            bufferedInputStream = BufferedInputStream(objectStream)
            var i = 0
            while ((bufferedInputStream.read(buff).also { i = it }) != -1) {
                outputStream.write(buff, 0, i)
                outputStream.flush()
            }
        } catch (e: IOException) {
            e.printStackTrace()
        } finally {
            try {
                bufferedInputStream!!.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }

    }

    // 文件分片初始化
    @RequestMapping(value = ["/{bucketName}/**"], method = [RequestMethod.POST], params = ["uploads"])
    fun createMultipartUpload(@PathVariable bucketName:String, request: HttpServletRequest):ResponseEntity<Any> {
        val bucketName = URLDecoder.decode(bucketName, "utf-8")
        var pageUrl = URLDecoder.decode(request.requestURI, "utf-8")
        if (pageUrl.contains("\\?")) {
            pageUrl = pageUrl.split("\\?".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0]
        }
        val objectKey = pageUrl.replace(request.contextPath + "/s3/" + bucketName + "/", "")
        val result = s3Service.initiateMultipartUpload(bucketName, objectKey)

        var xml = ""
        val doc = DocumentHelper.createDocument()
        val root = doc.addElement("InitiateMultipartUploadResult")
        val bucket = root.addElement("Bucket")
        bucket.text = bucketName
        val key = root.addElement("Key")
        key.text = objectKey
        val uploadId = root.addElement("UploadId")
        uploadId.text = result.uploadId
        val format = OutputFormat.createPrettyPrint()
        format.encoding = "utf-8"
        val out = StringWriter()
        val writer = XMLWriter(out, format)
        writer.write(doc)
        writer.close()
        xml = out.toString()
        out.close()
        return ResponseEntity.ok().contentType(MediaType.APPLICATION_XML).body(xml)
    }
    //文件分片上传
    @RequestMapping(value = ["/{bucketName}/**"], method = [RequestMethod.PUT], params = ["partNumber", "uploadId"])
    fun uploadPart(@PathVariable bucketName: String, request: HttpServletRequest, response: HttpServletResponse): ResponseEntity<String> {
        val bucketName = URLDecoder.decode(bucketName, "utf-8")
        var pageUrl = URLDecoder.decode(request.requestURI, "utf-8")
        if (pageUrl.contains("\\?")) {
            pageUrl = pageUrl.split("\\?".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0]
        }
        val objectKey = pageUrl.replace(request.contextPath + "/s3/" + bucketName + "/", "")
        val partNumber = ConvertOp.convert2Int(request.getParameter("partNumber"))
        val uploadId = request.getParameter("uploadId")
        val eTag = s3Service.uploadPart(bucketName, objectKey, partNumber, uploadId, request.inputStream)
        response.addHeader("ETag", eTag.geteTag())
        return ResponseEntity.ok().build()

    }
    // 合并分片
    @RequestMapping(value = ["/{bucketName}/**"], method = [RequestMethod.POST], params = ["uploadId"])
    fun completeMultipartUpload(@PathVariable bucketName: String, request: HttpServletRequest): ResponseEntity<String> {
        val bucketName = URLDecoder.decode(bucketName, "utf-8")
        var pageUrl = URLDecoder.decode(request.requestURI, "utf-8")
        if (pageUrl.contains("\\?")) {
            pageUrl = pageUrl.split("\\?".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0]
        }
        val objectKey = pageUrl.replace(request.contextPath + "/s3/" + bucketName + "/", "")
        val uploadId = request.getParameter("uploadId")
        val partETags = ArrayList<PartETag>()

        val reader = SAXReader()
        val bodyDoc = reader.read(request.inputStream)
        val bodyRoot = bodyDoc.rootElement
        val elementList: MutableList<Element> = bodyRoot.elements("Part") as MutableList<Element>
        for (element in elementList) {
            val partNumber = ConvertOp.convert2Int(element.element("PartNumber").text)
            val eTag = element.element("ETag").text
            val partETag = PartETag(partNumber, eTag)
            partETags.add(partETag)
        }
        val result = s3Service.completeMultipartUpload(bucketName, objectKey, uploadId, CompleteMultipartUpload(partETags))
        var xml = ""
        val doc = DocumentHelper.createDocument()
        val root = doc.addElement("CompleteMultipartUploadResult")
        val location = root.addElement("Location")
        location.text = CommonUtil.apiPath() + "s3/" + bucketName + "/" + objectKey
        val bucket = root.addElement("Bucket")
        bucket.text = bucketName
        val key = root.addElement("Key")
        key.text = objectKey
        val etag = root.addElement("ETag")
        etag.text = result.geteTag()

        val format = OutputFormat.createPrettyPrint()
        format.encoding = "utf-8"
        val out = StringWriter()
        val writer = XMLWriter(out, format)
        writer.write(doc)
        writer.close()
        xml = out.toString()
        out.close()
        return ResponseEntity.ok().contentType(MediaType.APPLICATION_XML).body(xml)
    }

}


@RestController
@RequestMapping("/s3")
class S3ExtController(
    private val s3Service: S3Service,
) {
    private val log = LoggerFactory.getLogger(S3ExtController::class.java)
    // 桶详情
    @RequestMapping(value = ["/{bucketName}"], method = [RequestMethod.HEAD])
    fun headBucket(@PathVariable bucketName: String):ResponseEntity<Any> {
        val bucketName = URLDecoder.decode(bucketName, "utf-8")
        return if (s3Service.headBucket(bucketName)) {
            ResponseEntity.ok().build()
        } else {
            ResponseEntity.notFound().build()
        }
    }

    // 桶列表
    @GetMapping("/")
    fun listBucket(): ResponseEntity<String> {
        var xml = ""
        val result = ListBucketsResult(s3Service.listBuckets())
        val doc = DocumentHelper.createDocument()
        val root = doc.addElement("ListAllMyBucketsResult")
        val owner = root.addElement("Owner")
        val id = owner.addElement("ID")
        id.text = "20230529"
        val displayName = owner.addElement("DisplayName")
        displayName.text = "admin"
        val buckets = root.addElement("Buckets")
        for (item in result.buckets!!) {
            val bucket = buckets.addElement("Bucket")
            val name = bucket.addElement("Name")
            name.text = item.name
            val creationDate = bucket.addElement("CreationDate")
            creationDate.text = item.creationDate
        }
        val format = OutputFormat.createPrettyPrint()
        format.encoding = "utf-8"
        val out = StringWriter()
        val writer = XMLWriter(out, format)
        writer.write(doc)
        writer.close()
        xml = out.toString()
        out.close()
        return ResponseEntity.ok().contentType(MediaType.APPLICATION_XML).body(xml)
    }

    // 文件详情
    @RequestMapping(value = ["/{bucketName}/**"], method = [RequestMethod.HEAD])
    fun headObject(@PathVariable bucketName: String, request: HttpServletRequest, response: HttpServletResponse): ResponseEntity<Any> {
        val bucketName = URLDecoder.decode(bucketName, "utf-8")
        var pageUrl = URLDecoder.decode(request.getRequestURI(), "utf-8")
        if (pageUrl.contains("\\?")) {
            pageUrl = pageUrl.split("\\?".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0]
        }
        val objectKey: String =
            pageUrl.replace(request.getContextPath() + "/s3/" + bucketName + "/", "").replace("/metadata", "")
        val headInfo = s3Service.headObject(bucketName, objectKey)
        if (headInfo.containsKey("NoExist")) {
            return ResponseEntity.notFound().build()
        } else {
            for (key in headInfo.keys) {
                response.addHeader(key, headInfo[key])
            }
            return ResponseEntity.ok().build()
        }

    }

    // 文件列表
    @GetMapping("/{bucketName}")
    fun listObjects(@PathVariable bucketName: String, request: HttpServletRequest): ResponseEntity<String> {
        val bucketName = URLDecoder.decode(bucketName, "utf-8")
        var xml = ""
        val prefix = ConvertOp.convert2String(request.getParameter("prefix"))
        val s3ObjectList = s3Service.listObjects(bucketName, prefix)
        val doc = DocumentHelper.createDocument()
        val root = doc.addElement("ListObjectsResult")
        val name = root.addElement("Name")
        name.text = bucketName
        val prefixElement = root.addElement("Prefix")
        prefixElement.text = prefix
        val isTruncated = root.addElement("IsTruncated")
        isTruncated.text = "false"
        val maxKeys = root.addElement("MaxKeys")
        maxKeys.text = "100000"
        for (s3Object in s3ObjectList) {
            val contents = root.addElement("Contents")
            val key = contents.addElement("Key")
            key.text = s3Object.key
            if (!StringUtil.isEmpty(s3Object.metadata!!.lastModified)) {
                val lastModified = contents.addElement("LastModified")
                lastModified.text = s3Object.metadata!!.lastModified
                val size = contents.addElement("Size")
                size.text = "${s3Object.metadata!!.contentLength}"
            }
        }

        val format = OutputFormat.createPrettyPrint()
        format.encoding = "utf-8"
        val out = StringWriter()
        val writer = XMLWriter(out, format)
        writer.write(doc)
        writer.close()
        xml = out.toString()
        out.close()
        return ResponseEntity.ok().contentType(MediaType.APPLICATION_XML).body(xml)
    }
}